﻿using MaterialTypeRecognition.Shell.SaveInfoService;
using System;
using System.Collections.Concurrent;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using WebMiddleware.SaveInfoService;
using WebMiddleware.Utils;

namespace WebMiddleware.Store
{
    /// <summary>
    /// 类型映射到本地文件存储。
    /// </summary>
    public class TypeSaveInfoStore : IStore<Stream>
    {
        private readonly ConcurrentDictionary<Type, string> _fileMap;

        // 文件锁。
        private readonly ConcurrentDictionary<string, Mutex> _fileLocks;

        private readonly object _fileLocksLock = new object();

        public TypeSaveInfoStore()
        {
            _fileMap = new ConcurrentDictionary<Type, string>();
            _fileLocks = new ConcurrentDictionary<string, Mutex>();
        }

        private Mutex GetFileLock(string fileName)
        {
            lock (_fileLocksLock)
            {
                if (!_fileLocks.TryGetValue(fileName, out var fileLock))
                {
                    fileLock = new Mutex();
                    _fileLocks.TryAdd(fileName, fileLock);
                }

                return fileLock;
            }
        }

        public void Rent<TType>(Action<Stream> consume) where TType : class, new()
        {
            string? fileName;

            if (!_fileMap.TryGetValue(typeof(TType), out fileName))
            {
                fileName = GetTypeMapFile(typeof(TType));
                _fileMap.TryAdd(typeof(TType), fileName);
            }

            var fileLock = GetFileLock(fileName); // 获取文件级别的锁

            fileLock.WaitOne(); // 等待获取锁

            try
            {
                var stream = JsonFile.OpenStream(fileName);
                consume(stream);
                stream.Close();
            }
            finally
            {
                fileLock.ReleaseMutex(); // 释放锁
            }
        }

        public TType Rent<TType>(Func<Stream, TType> consume) where TType : class, new()
        {
            TType returnValue = default!;

            Rent<TType>(stream =>
            {
                returnValue = consume(stream);
            });

            return returnValue;
        }

        public async Task RentAsync<TType>(Action<Stream> consume) where TType : class, new()
        {
            await Task.Run(() =>
            {
                Rent<TType>(consume);
            });
        }

        public async Task<TType> RentAsync<TType>(Func<Stream, TType> consume) where TType : class, new()
        {
            return await Task.Run(() =>
            {
                return Rent<TType>(consume);
            });
        }

        /// <summary>
        /// 获取类型映射的文件名。
        /// </summary>
        private string GetTypeMapFile(Type type)
        {
            var attrs = type.GetCustomAttributes();
            var typeMapFile = attrs.OfType<TypeMapFileAttribute>().FirstOrDefault();
            var invalidPathChars = Path.GetInvalidPathChars();
            var invalidFileNameChars = Path.GetInvalidFileNameChars();
            var fileName = typeMapFile?.FileName ?? type.FullName;

            if (string.IsNullOrWhiteSpace(fileName))
            {
                throw new InvalidOperationException($"{type.FullName}类型映射至文件名为空。");
            }

            // 将文件名拆开分别校验。
            var originDir = Path.GetDirectoryName(fileName);

            if (originDir == null)
            {
                throw new InvalidOperationException($"{type.FullName}类型映射目录无效。");
            }

            var originFileName = Path.GetFileName(fileName);

            // 校验文件目录的合法性。
            if (originDir.Any(charValue => invalidPathChars.Contains(charValue)))
            {
                throw new InvalidOperationException($"{type.FullName}类型映射文件名无效,映射文件名为{fileName}。");
            }

            // 校验文件名名称合法性。
            if (originFileName.Any(charValue => invalidFileNameChars.Contains(charValue)))
            {
                throw new InvalidOperationException($"{type.FullName}类型映射文件名无效,映射文件名为{fileName}。");
            }

            var baseDir = typeMapFile?.BaseDirectory;

            // 校验基目录合法性。
            if (!string.IsNullOrWhiteSpace(baseDir)
                && baseDir.Any(charValue => invalidPathChars.Contains(charValue)))
            {
                throw new InvalidOperationException($"{type.FullName}类型映射基目录无效，映射基目录为{baseDir}。");
            }

            // 如果 .json 没有扩展名，追加 .json 扩展名。
            if (!string.Equals(Path.GetExtension(fileName), ".json", StringComparison.OrdinalIgnoreCase))
            {
                var strBuilder = new StringBuilder(fileName);
                strBuilder.Append(".json");
                fileName = strBuilder.ToString();
            }

            // 默认从 exe 目录开始引导。
            var exeDir = AppDomain.CurrentDomain.BaseDirectory;

            // 注意：如果 baseDir 指定了盘符，那么 exeDir 将时无效的哟。
            fileName = Path.Combine(exeDir, baseDir ?? string.Empty, fileName);

            return fileName;
        }
    }
}
